package com.alinesno.cloud.monitor.agent.collect;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;

import com.alibaba.fastjson.JSONObject;
import com.alinesno.cloud.compoment.kafka.stragy.KafkaComponentStragy;
import com.alinesno.cloud.monitor.agent.aspect.BaseAspect;
import com.alinesno.cloud.monitor.agent.dto.runtime.MethodDto;
import com.alinesno.cloud.monitor.agent.dto.runtime.MethodExceptionDto;
import com.alinesno.cloud.monitor.agent.tool.IPUtils;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.crypto.SecureUtil;

/**
 * 监控服务运行方法
 * 
 * @author WeiXiaoJin
 * @since 2019年9月17日 下午10:04:28
 */
@Aspect
public class WatcherServiceMethonAspect extends BaseAspect {

	@SuppressWarnings("unused")
	private static final Logger log = LoggerFactory.getLogger(WatcherServiceMethonAspect.class);

	// 方法统计map
	private static Map<String, MethodDto> methodMap = new HashMap<String, MethodDto>();

	/**
	 * @within(org.springframework.stereotype.Service)，拦截带有 @Service 注解的类的所有方法
	 * @annotation(org.springframework.web.bind.annotation.RequestMapping)，拦截带有@RquestMapping的注解方法
	 */
	@Pointcut("execution(public * com.alinesno.cloud..*.*(..))")
//	@Pointcut("@within(org.springframework.stereotype.Service)")
	private void servicePointcut() {
	}

	@Around("servicePointcut()")
	public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
		long startTime = System.currentTimeMillis();
		Object obj = null;
		Throwable catchE = null;
		try {
			obj = pjp.proceed();
		} catch (Throwable e) {
			catchE = e;
		}
		long useTime = System.currentTimeMillis() - startTime;

		final Throwable catchEF = catchE;
		if (kafkaProducer != null) {
			ThreadUtil.excAsync(new Runnable() {

				@Override
				public void run() {

					MethodDto dto = buildMethodMessage(pjp, useTime, catchEF);
					if (dto != null) {
						// 发送消息
						kafkaProducer.asyncSendMessage(WATCHER_TOPIC_METHOD_RUNTIME, JSONObject.toJSONString(dto));

						// 发送异常消息
						if (catchEF != null) {
							// String errorStack = ExceptionUtils.getStackTrace(catchEF);
							String message = catchEF.getMessage();
							MethodExceptionDto eDto = new MethodExceptionDto();

							BeanUtils.copyProperties(dto, eDto);

							eDto.setErrorMessage(message);

							// eDto.setErrorStack(errorStack);

							kafkaProducer.asyncSendMessage(WATCHER_TOPIC_METHOD_EXCEPTION,
									JSONObject.toJSONString(eDto));
						}
					}
				}
			}, false);
		}

		if (catchE != null) {
			throw catchE;
		}

		return obj;
	}

	private MethodDto buildMethodMessage(JoinPoint pjp, long useTime, Throwable catchE) {
		MethodSignature signature = (MethodSignature) pjp.getSignature();

		String className = pjp.getTarget().getClass().getName(); // 类名

		// ------------->>>>>>>>> 忽略信息
		if (className == null 
				|| className.equals(KafkaComponentStragy.class.getName())
				|| className.equals("com.alinesno.cloud.base.logger.consumer.MontorMethodLoggerConsumerListener")
				|| className.equals("com.alinesno.cloud.base.logger.service.impl.LogMethodRuntimeServiceImpl")
				|| className.startsWith("com.sun.proxy") 
				|| className.startsWith("org.apache.dubbo.common.bytecode")) {
			return null;
		}

		String typeName = pjp.getTarget().getClass().getSimpleName(); // 类型
		if (typeName.contains("SpringCGLIB")) {
			int index = typeName.indexOf("$$", 2);
			typeName = typeName.substring(0, index);
			className = typeName ; 
		}
		// ------------->>>>>>>>> 忽略信息

		String packageName = pjp.getTarget().getClass().getPackage().getName(); // 包名
		String methodName = signature.getName(); // 方法名
		Object[] params = signature.getParameterNames(); // 参数

		// 通过【包包+类名+方法名+参数】为唯一码
		String methodKey = SecureUtil.md5(packageName + className + methodName + Arrays.toString(params));

//		log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.");
//		log.info("packageName:{} , className:{} , methodName:{} , Arrays.toString(params):{}" , packageName , className , methodName , Arrays.toString(params));
//		log.info("method_key:{}" , methodKey);
//		log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.");

		MethodDto dto = methodMap.get(methodKey);
		if (dto == null) {
			dto = new MethodDto();

			String hostname = IPUtils.HOSTNAME;
			String ip = IPUtils.IP;

			dto.setMethodParams(params);
			dto.setApplicationName(applicationName);
			dto.setMethodTime(useTime);
			dto.setHostname(hostname);
			dto.setIp(ip);
			dto.setCreateTime(System.currentTimeMillis());
			dto.setClassName(className);
			dto.setMethodName(methodName);
			dto.setMax(useTime);
			dto.setMin(useTime);

			dto.setMethodKey(methodKey);
			dto.setType(typeName);
		}

		// >>>>>>>>>>>>>>>> 统计方法_start >>>>>>>>>>>>>>>>>>>>>
		dto.setCount(dto.getCount() + 1);
		if (catchE == null) {
			dto.setOk(1);
		} else {
			dto.setFail(1);
		}
		dto.setTotalrunTime(dto.getTotalrunTime() + useTime);
		dto.setAvg(Double.parseDouble(calculateResultOfPercent(dto.getTotalrunTime(), dto.getCount())));
		dto.setMin(useTime < dto.getMin() ? useTime : dto.getMin());
		dto.setMax(useTime > dto.getMax() ? useTime : dto.getMax());

		long createTime = dto.getCreateTime();
		long currentTime = System.currentTimeMillis();
		long minTime = currentTime - createTime;

		dto.setRps(dto.getRps() + 1);

		if (minTime > 60000) { // 每分钟统计
			dto.setCreateTime(currentTime);
			dto.setRps(0);
		}

		methodMap.put(methodKey, dto);
		// >>>>>>>>>>>>>>>> 统计方法_end >>>>>>>>>>>>>>>>>>>>>

		return dto;
	}

//	方法名(Method)
//	类型(接口/实类/数据层)(Type)
//	层次(Level)
//	每分钟调用次数(TimePercent)
//  每秒请求数(RPS)
//	总请求数(Count)
//	平均响应时间(Avg)
//	最小响应时间(Min)
//	最大响应时间(Max)
//	异常次数(Fail)
//	成功次数(OK)
//	
//	Method[6]                            Type        Level  TimePercent      RPS  Avg(ms)  Min(ms)  Max(ms)    StdDev    Count     TP50     TP90     TP95     TP99    TP999   TP9999
//	DemoServiceImpl.getId2(long)      General      Service      322.50%     6524     0.49        0        1     0.50      6524        0        1        1        1        1        1
//	DemoServiceImpl.getId3(long)      General      Service      296.10%     4350     0.68        0        1     0.47      4350        1        1        1        1        1        1
//	DemoServiceImpl.getId4(long)      General      Service      164.60%     2176     0.76        0        1     0.43      2176        1        1        1        1        1        1
//	DemoServiceImpl.getId1(long)      General      Service        0.00%     8704     0.00        0        0     0.00      8704        0        0        0        0        0        0
//	DemoDAO.getId1(long)         DynamicProxy          DAO        0.00%     2176     0.00        0        0     0.00      2176        0        0        0        0        0        0
//	DemoDAO.getId2()             DynamicProxy          DAO        0.00%     2176     0.00        0        0     0.00      2176        0        0        0        0        0        0

//	/**
//	 * 拦截service层异常，记录异常日志，并设置对应的异常信息 目前只拦截Exception，是否要拦截Error需再做考虑
//	 * 
//	 * @param e 异常对象
//	 */
//	@AfterThrowing(pointcut = "servicePointcut()", throwing = "e")
//	public void handle(JoinPoint pjp, Exception e) {
//		if (e != null && kafkaProducer != null) {
//
//			ThreadUtil.excAsync(new Runnable() {
//				@Override
//				public void run() {
//
//					MethodDto dto = buildMethodMessage(pjp, 0 , e);
//					if (dto != null) {
//
//						String errorStack = ExceptionUtils.getStackTrace(e);
//						String message = e.getMessage();
//						MethodExceptionDto eDto = new MethodExceptionDto();
//
//						BeanUtils.copyProperties(dto, eDto);
//
//						eDto.setErrorMessage(message);
//						eDto.setErrorStack(errorStack);
//
//						// 发送消息
//						kafkaProducer.asyncSendMessage(WATCHER_TOPIC_METHOD_EXCEPTION, JSONObject.toJSONString(eDto));
//					}
//
//				}
//			}, false);
//
//		}
//	}

}
